﻿using System;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.Security;
using AutoMapper;
using MiddleMvcTask.BL.Helpers;
using MiddleMvcTask.BL.Interfaces;
using MiddleMvcTask.Data.DataModels;
using MiddleMvcTask.Data;
using MiddleMVCTask.BL.ViewModels;
using System.Collections.Generic;
using MiddleMVCTask.BL.Interfaces;

namespace MiddleMVCTask.BL
{
    public class UserBL : IUserBL
    {
        /// <summary>
        /// Represent a instance of data layer
        /// </summary>
        private IRepository<User> Data { get; set; }
        /// <summary>
        /// Represent a instance of authenticetion layer
        /// </summary>
        private IAuth Auth { get; set; }
               
        /// <summary>
        /// Show if given method was executed without errors
        /// Contains all errors that was generated by given method.
        /// </summary>
        public Result Result { get; private set; }

        public UserBL(IRepository<User> data, IAuth auth)
        {
            this.Data = data;
            this.Auth = auth;
            this.Result = new Result();
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<RegisterViewModel, User>();
            });
        }
      
        /// <summary>
        /// Create a new user by given RegisterViewModel.
        /// In case of invalid user data it add erros to Result property
        /// </summary>
        /// <param name="user">Represent new user data</param>
        public void CreateUser(RegisterViewModel user)
        {
            ValidateNewUser(user);

            if (this.Result.IsValid)
            {
                var userEntity = Mapper.Map<User>(user);
                userEntity.Password = Hash(userEntity.Password);

                AddToCache(userEntity);
                this.Data.Add(userEntity);
                this.Data.Save();
            }
        }
        
        /// <summary>
        /// Login user by given LoginViewModel.
        /// In case of invalid user data it add erros to Result property
        /// </summary>
        /// <param name="user"></param>
        public void LoginUser(LoginViewModel user)
        {
            var cashedUsers = HttpRuntime.Cache["users"] as ICollection<User>;
            
            var ifUserExist = cashedUsers.Any(u => u.Username.Equals(user.Username));
            if (ifUserExist)
            {
                var ifCorrectPass = cashedUsers
                                               .Any(u => u.Username == user.Username &&
                                                         u.Password == Hash(user.Password));
                
                if (ifCorrectPass)
                {
                    this.Result.IsValid = true;
                    this.Auth.DoAuth(user.Username, false);
                }
                else
                {
                    this.Result.IsValid = false;
                    this.Result.Errors.Add("Incorrect username/password");
                }
            }
            else
            {
                this.Result.IsValid = false;
                this.Result.Errors.Add("Username does not exist");
            }
        }

        /// <summary>
        /// Add new user data to Cashe
        /// </summary>
        /// <param name="userEntity"></param>
        private void AddToCache(User userEntity)
        {
            var cashedUsers = HttpRuntime.Cache["users"] as ICollection<User>;
            cashedUsers.Add(userEntity);

            HttpRuntime.Cache.Insert(
                /* key */                "users",
                /* value */              cashedUsers,
                /* dependencies */       null,
                /* absoluteExpiration */ Cache.NoAbsoluteExpiration,
                /* slidingExpiration */  Cache.NoSlidingExpiration,
                /* priority */           CacheItemPriority.NotRemovable,
                /* onRemoveCallback */   null);
        }

        /// <summary>
        /// Vilidate new user data
        /// </summary>
        /// <param name="user"></param>
        private void ValidateNewUser(RegisterViewModel user)
        {
            var cashedUsers = HttpRuntime.Cache["users"] as ICollection<User>;

            var ifExist = cashedUsers.Any(u => u.Username == user.Username);

            if (ifExist)
            {
                this.Result.IsValid = false;
                this.Result.Errors.Add("Unable to sign up");
            }
            else
            {
                this.Result.IsValid = true;
            }
        }

        /// <summary>
        /// Hash user password.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static string Hash(string value)
        {
            return Convert.ToBase64String(
                System.Security
                      .Cryptography
                      .SHA256
                      .Create()
                      .ComputeHash(System.Text.Encoding.UTF8.GetBytes(value)));
        }
    }
}